/* exec-mathexpress 1.0.15 
*  数学表达式解析
*  https://www.npmjs.com/package/exec-mathexpress
*/

//欧几里得算法 求两个数a、b的最大公约数
function gcd(a, b) {
  return b === 0 ? a : gcd(b, a % b);
}
//分数类 分子，分母
class Fraction {
  static create(num, den = 1) {
    if (num instanceof Fraction) {
      return num;
    } else if (/(-?\d+)\/(\d+)/.test(num)) {
      return new Fraction(parseInt(RegExp.$1), parseInt(RegExp.$2));
    } else {
      if (/\.(\d+)/.test(num)) {
        num = num * Math.pow(10, RegExp.$1.length);
        den = den * Math.pow(10, RegExp.$1.length);
      }
      if (/\.(\d+)/.test(den)) {
        num = num * Math.pow(10, RegExp.$1.length);
        den = den * Math.pow(10, RegExp.$1.length);
      }
      return new Fraction(num, den);
    }
  }
  constructor(num = 0, den = 1) {
    if (den < 0) {
      num = -num;
      den = -den;
    }
    if (den === 0) {
      throw '分母不能为0';
    }
    let g = gcd(Math.abs(num), den);
    this.num = num / g;
    this.den = den / g;
  }
  //加
  add(o) {
    return new Fraction(this.num * o.den + this.den * o.num, this.den * o.den);
  }
  //减
  sub(o) {
    return new Fraction(this.num * o.den - this.den * o.num, this.den * o.den);
  }
  //乘
  multiply(o) {
    return new Fraction(this.num * o.num, this.den * o.den);
  }
  //除
  divide(o) {
    return new Fraction(this.num * o.den, this.den * o.num);
  }
  //小于
  lessThan(o) {
    return this.num * o.den < this.den * o.num;
  }
  //大于
  greaterThan(o) {
    return this.num * o.den > this.den * o.num;
  }
  //等于
  equal(o) {
    return this.num * o.den === this.den * o.num;
  }
  toString() {
    return this.num + '/' + this.den;
  }
}

//解析数学表达式
function execMathExpress(formula, obj) {
  //局部变量
  const tempObj = Object.assign(
    {
      _0: 0
    },
    obj
  );
  //计算缓存
  const keyCache = {};
  let index = 1;

  formula = formula.replace(/ /g, ''); //清理空格
  //解析数字
  formula = formula.replace(/(^|[(*+/-])(\d+\.\d+|\d+)/g, function(m, p1, p2) {
    if (keyCache[p2]) {
      return p1 + keyCache[p2];
    }
    const key = (keyCache[p2] = '_' + index++);
    tempObj[key] = Fraction.create(p2);
    return p1 + key;
  });

  function getKey(p1, p2, p3) {
    const keyC = p1 + p2 + p3;
    if (keyCache[keyC]) {
      return keyCache[keyC];
    }
    const key = (keyCache[keyC] = '_' + index++);
    const fA = Fraction.create(tempObj[p1]);
    const fB = Fraction.create(tempObj[p3]);
    switch(p2){
      case '+':
        tempObj[key] = fA.add(fB);
      break;
      case '-':
        tempObj[key] = fA.sub(fB);
      break;
      case '*':
        tempObj[key] = fA.multiply(fB);
      break;
      case '/':
        tempObj[key] = fA.divide(fB);
        break;
      case '<':        
        tempObj[key]=fA.lessThan(fB);
        break;
      case '>':
        tempObj[key]=fA.greaterThan(fB);
        break;
      case '=':
        tempObj[key]=fA.equal(fB);
        break;
      // default:
      //   tempObj[key] = fA.add(fB);
    }   
    return key;
  }
  function run(s) {
    //子表达式
    if (/\(([^\(]+?)\)/.test(s)) {
      s = s.replace(/\(([^\(]+?)\)/g, function(m, p1, p2) {
        return run(p1);
      });
    }
    //负号
    s = s.replace(/([*/+]|^)-(\w+)/g, function(m, p1, p2) {
      return getKey('_0', '-', p2);
    });
    //返回
    if (/(^\w+$)/.test(s)) {
      return RegExp.$1;
    }
    //操作符解析：乘法、除法、加法、减法
    const expArr = ['+', '-','*', '/', '<','=','>'];
    for (let i = 0; i < expArr.length; i++) {
      const p = expArr[i];
      const reg = new RegExp('(\\w+)[' + p + '](\\w+)');
      while (reg.test(s)) {
        s = s.replace(reg, function(m, p1, p2) {
          return getKey(p1, p, p2);
        });
      }
    }
    //返回
    if (/(^\w+$)/.test(s)) {
      return RegExp.$1;
    }
    return run(s);
  }
  return tempObj[run(formula)];
}
module.exports = execMathExpress;
